/*
This is part of Screenbar, which is based in part on the Litestep
shell source code.

Copyright (C) 2008-2009 Jim Babcock
Copyright (C) 1997-2009 The LiteStep Development Team

This program is free software; you can redistribute it and/or
modify it under the terms of the GNU General Public License
as published by the Free Software Foundation; either version 2
of the License, or (at your option) any later version.

This program is distributed in the hope that it will be useful,
but WITHOUT ANY WARRANTY; without even the implied warranty of
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
GNU General Public License for more details.

You should have received a copy of the GNU General Public License
along with this program; if not, write to the Free Software
Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA  02111-1307, USA.
*/
#include "stdafx.h"

/////////////////////////////////////////////////////////////////////////////

AnimationPool *animationPool = NULL;

AnimationPool::AnimationPool()
{
}

AnimationPool::~AnimationPool()
{
	for(map<string, Animation*>::iterator ii=animations.begin(); ii!=animations.end(); ii++)
		delete ii->second;
	animations.clear();
}

Animation *AnimationPool::getAnimation(string prefix)
{
	if(prefix=="" || prefix=="none" || prefix=="null")
		return NULL;
	
	map<string, Animation*>::iterator it = animations.find(prefix);
	if(it != animations.end()) {
		return it->second;
	} else {
		Animation *animation = createAnimation(prefix);
		animations[prefix] = animation;
		return animation;
	}
}

Animation *AnimationPool::createAnimation(string prefix)
{
	string effect = to_lower(getConfigString("Effect", NULL, prefix.c_str()));

	if(effect == "fadein") {
		return new FadeAnimation(prefix, true);
	} else if(effect == "fadeout") {
		return new FadeAnimation(prefix, false);
	} else if(effect == "slidein") {
		return new SlideAnimation(prefix, true);
	} else if(effect == "slideout") {
		return new SlideAnimation(prefix, false);
	} else if(effect == "sequence") {
		return new SequenceAnimation(prefix);
	} else if(effect == "shiftin") {
		return new ShiftAnimation(prefix, true);
	} else if(effect == "shiftout") {
		return new ShiftAnimation(prefix, false);
	} else if(effect == "none") {
		return new NullAnimation(prefix);
	} else {
		warn(retprintf("Unrecognized animation effect '%s' for animation '%s'.",
			effect.c_str(), prefix.c_str()).c_str());
		return new NullAnimation(prefix);
	}
}

/////////////////////////////////////////////////////////////////////////////

AnimationState::AnimationState()
{
	animation = NULL;
	startTime = 0;
	timeOffset = 0;
	direction = directionUp;
}

void AnimationState::updateTime()
{
	UINT64 currentTime = getSystemTime();
	timeOffset = (currentTime-startTime) / 10000;
}

/////////////////////////////////////////////////////////////////////////////

Animation::Animation(string prefix)
{
	this->prefix = prefix;
	duration = getConfigInt("Duration", 500, prefix.c_str());

	this->direction = parseDirection(getConfigString("AnimationDirection", "auto", prefix.c_str()));
}

void Animation::apply(AnimationState *state, HDC source, HDC dest,
	int x, int y, int width, int height)
{
	BitBlt(dest, 0, 0, width, height, source, 0, 0, SRCCOPY);
}

float Animation::getAlpha(AnimationState *state)
{
	return 1.0;
}

ElementSize Animation::getElementSize(AnimationState *state, ElementSize childSize)
{
	return childSize;
}

bool Animation::isFinished(AnimationState *state)
{
	return state->timeOffset >= duration;
}

int Animation::getDuration()
{
	return duration;
}

string Animation::getPrefix()
{
	return prefix;
}

Animation::RequiredTransparency Animation::getRequiredTransparency()
{
	return transAny;
}

bool Animation::requiresTransfer()
{
	return false;
}

int Animation::reverseTimeOffset(Animation *reverseAnimation, int timeOffset)
{
	float fractionComplete = (float)timeOffset / duration;
	return reverseAnimation->duration * (1.0-fractionComplete);
}

/////////////////////////////////////////////////////////////////////////////

NullAnimation::NullAnimation(string prefix)
	:Animation(prefix)
{
}

/////////////////////////////////////////////////////////////////////////////

SequenceAnimation::SequenceAnimation(string prefix)
	:Animation(prefix)
{
	cyclic = getConfigBool("Cyclic", false, prefix.c_str());
	duration = 0;
	
	string childNameList = getConfigLine("Steps", NULL, prefix.c_str());
	vector<string> childNames;
	tokenizeString(childNameList, childNames, " ,;");
	
	for(unsigned ii=0; ii<childNames.size(); ii++)
	{
		Animation *child = animationPool->getAnimation(childNames[ii]);
		children.push_back(child);
		duration += child->getDuration();
	}
}

void SequenceAnimation::apply(AnimationState *state, HDC source, HDC dest, int x, int y, int width, int height)
{
	pair<int,Animation*> sequencePos = getSequencePos(state);
	
	AnimationState childState = *state;
	childState.timeOffset = sequencePos.first;
	childState.animation = sequencePos.second;
	childState.animation->apply(&childState, source, dest, x, y, width, height);
}

float SequenceAnimation::getAlpha(AnimationState *state)
{
	pair<int,Animation*> sequencePos = getSequencePos(state);
	
	AnimationState childState = *state;
	childState.timeOffset = sequencePos.first;
	childState.animation = sequencePos.second;
	return childState.animation->getAlpha(&childState);
}

Animation::RequiredTransparency SequenceAnimation::getRequiredTransparency()
{
	RequiredTransparency ret = transAny;
	
	for(unsigned ii=0; ii<children.size(); ii++)
	{
		RequiredTransparency childTransType = children[ii]->getRequiredTransparency();
		if(childTransType > ret)
			ret = childTransType;
	}
	
	return ret;
}

ElementSize SequenceAnimation::getElementSize(AnimationState *state, ElementSize childSize)
{
	pair<int,Animation*> sequencePos = getSequencePos(state);
	
	AnimationState childState = *state;
	childState.timeOffset = sequencePos.first;
	childState.animation = sequencePos.second;
	return childState.animation->getElementSize(&childState, childSize);
}

pair<int,Animation*> SequenceAnimation::getSequencePos(AnimationState *state)
{
	int timeOffset = state->timeOffset;
	int animIndex = 0;
	
	if(cyclic && timeOffset>duration)
	{
		if(duration > 0)
			timeOffset -= duration * (timeOffset/duration);
		else
			timeOffset = 0;
	}
	
	while(animIndex+1 < (int)children.size() && timeOffset > children[animIndex]->getDuration())
	{
		animIndex++;
		timeOffset -= children[animIndex]->getDuration();
	}
	
	return pair<int,Animation*>(timeOffset, children[animIndex]);
}

/////////////////////////////////////////////////////////////////////////////

FadeAnimation::FadeAnimation(string prefix, bool fadeIn)
	:Animation(prefix)
{
	this->fadeIn = fadeIn;
}

void FadeAnimation::apply(AnimationState *state, HDC source, HDC dest, int x, int y, int width, int height)
{
	BLENDFUNCTION blendFunc;
	blendFunc.BlendOp = AC_SRC_OVER;
	blendFunc.BlendFlags = 0;
	blendFunc.SourceConstantAlpha = (int)(255*getAlpha(state));
	blendFunc.AlphaFormat = AC_SRC_ALPHA;
	
	AlphaBlend(dest, x, y, width, height, source, 0, 0, width, height, blendFunc);
}

float FadeAnimation::getAlpha(AnimationState *state)
{
	if(fadeIn) {
		float alpha = (float)state->timeOffset / duration;
		return min(1.0, max(0.0, alpha));
	} else {
		float alpha = 1.0 - (float)state->timeOffset / duration;
		return min(1.0, max(0.0, alpha));
	}
}

Animation::RequiredTransparency FadeAnimation::getRequiredTransparency()
{
	return transColorKey;
}

/////////////////////////////////////////////////////////////////////////////

SlideAnimation::SlideAnimation(string prefix, bool slideIn)
	:Animation(prefix)
{
	this->slideIn = slideIn;
}

void SlideAnimation::apply(AnimationState *state, HDC source, HDC dest, int x, int y, int width, int height)
{
	float fractionVisible = (float)state->timeOffset / duration;
	if(!slideIn) fractionVisible = 1-fractionVisible;
	
	bool vertical;
	if(state->direction == directionLeft || state->direction == directionRight)
		vertical = false;
	else vertical = true;
	
	int pxVisible = vertical ? fractionVisible*height : fractionVisible*width;
	
	BLENDFUNCTION blendFunc;
	blendFunc.BlendOp = AC_SRC_OVER;
	blendFunc.BlendFlags = 0;
	blendFunc.SourceConstantAlpha = 255;
	blendFunc.AlphaFormat = AC_SRC_ALPHA;
	
	Direction animDirection;
	if(this->direction != directionUnspecified)
		animDirection = this->direction;
	else
		animDirection = state->direction;
	
	switch(animDirection)
	{
		default:
		case directionUp:
			AlphaBlend(dest, x, y+height-pxVisible, width, pxVisible, source, 0, 0, width, pxVisible, blendFunc);
			break;
		case directionDown:
			AlphaBlend(dest, x, y, width, pxVisible, source, 0, height-pxVisible, width, pxVisible, blendFunc);
			break;
		case directionRight:
			AlphaBlend(dest, x, y, pxVisible, height, source, width-pxVisible, 0, pxVisible, height, blendFunc);
			break;
		case directionLeft:
			AlphaBlend(dest, x+width-pxVisible, y, pxVisible, height, source, 0, 0, pxVisible, height, blendFunc);
			break;
	}
}

Animation::RequiredTransparency SlideAnimation::getRequiredTransparency()
{
	return transColorKey;
}

/////////////////////////////////////////////////////////////////////////////

ShiftAnimation::ShiftAnimation(string prefix, bool enlarge)
	:Animation(prefix)
{
	this->enlarge = enlarge;
}

void ShiftAnimation::apply(AnimationState *state, HDC source, HDC dest, int x, int y, int width, int height)
{
}

ElementSize ShiftAnimation::getElementSize(AnimationState *state, ElementSize baseSize)
{
	float scaleFactor = (float)state->timeOffset / duration;
	if(!enlarge) scaleFactor = 1.0-scaleFactor;
	
	return ElementSize(baseSize.minimum*scaleFactor, baseSize.preferred*scaleFactor);
}
